/// <reference path="Xpcom.js">
///--------------------------------------------------------------------
/// <file name="BigInteger.js">
///   Encapsulates useful functions on large integers.
/// </file>
///--------------------------------------------------------------------

NAMFox.ns(function() { with (NAMFox) {

this.BigInteger = function(value) {
    /// <summary>
    ///   Initializes a new instance of the BigInteger class.
    /// </summary>
    /// <param name="value" type="String">
    ///   The string of hex digits with which to initialize this BigInteger.
    /// </param>
    

    if (value.constructor.name === "Array") {
        var upperBound = 0x7FFFFFFF;
        for (var i = 0; i < value.length; ++i) {
            var integer = value[i];
            if (typeof(integer) !== "number" || integer < 0 || integer > upperBound) {
                alert(typeof(integer) + " | " + integer);
                throw new TypeError("All items in the input array must be positive integers.");
            }
        }
        this.components = value;
        return;
    }
    
    if (typeof(value) === "number")  {
        value = value.toString(16);
    }
    
    if (typeof(value) === "string") {
        this.components = [];
        
        do {
            var sliceIndex = Math.max(0, value.length - 4);
            var component = value.substr(sliceIndex);
            value = value.substring(0, sliceIndex);
            
            var integer = parseInt(component, 16);
            
            // It's fine to push 0 if there are extra digits afterwards
            if (integer || parseInt(value, 16)) {
                this.components.push(parseInt(component, 16));
            }
        }
        while (value);
    }
    else {
        throw new TypeError("The value parameter must be an array of positive integers, a string of hex digits, or a number.");
    }
}

BigInteger.prototype = {
    components: null,
    
    _checkArgument: function(other) {
        if (typeof(other) === "number" && other >= 0x20000000000001) {
            throw new TypeError("Possible loss of precision in 'other' argument. Pass a String instead.");
        }
    },
    
    bitwiseAnd: function(other) {
        /// <summary>
        ///   Performs a bitwise and operation between two BigIntegers.
        /// </summary>
        /// <param name="other" type="BigInteger">
        ///   The BigInteger against which to do a bitwise and operation.
        /// </param>
        /// <returns type="BigInteger" />
        
        this._checkArgument(other);
        
        if (!other.components) {
            other = new BigInteger(other);
        }
        
        var components = this.components;
        var otherComponents = other.components;
        var upperBound = Math.min(components.length, otherComponents.length);
        
        var newComponents = [];
        for (var i = 0; i < upperBound; ++i) {
            newComponents[i] = components[i] & otherComponents[i];
        }
        
        return new BigInteger(newComponents);
    },
    
    bitwiseAndNot: function(other) {
        /// <summary>
        ///   Performs a bitwise and operation between this BigInteger and the
        ///   complement of another BigInteger.
        /// </summary>
        /// <param name="other" type="BigInteger">
        ///   The BigInteger whose complement serves as the other side of the
        ///   bitwise and operation.
        /// </param>
        /// <returns type="BigInteger" />
        
        this._checkArgument(other);
        
        if (!other.components) {
            other = new BigInteger(other);
        }
        
        var components = this.components;
        var otherComponents = other.components;
        var upperBound = Math.max(components.length, otherComponents.length);
        
        var newComponents = [];
        for (var i = 0; i < upperBound; ++i) {
            newComponents[i] = (components[i] || 0) & ~(otherComponents[i] || 0);
        }
        
        return new BigInteger(newComponents);
    },
    
    bitwiseOr: function(other) {
        /// <summary>
        ///   Performs a bitwise or operation between two BigIntegers.
        /// </summary>
        /// <param name="other" type="BigInteger">
        ///   The BigInteger against which to do a bitwise or operation.
        /// </param>
        /// <returns type="BigInteger" />
        
        this._checkArgument(other);
        
        if (!other.components) {
            other = new BigInteger(other);
        }
        
        var components = this.components;
        var otherComponents = other.components;
        var upperBound = Math.max(components.length, otherComponents.length);
        
        var newComponents = [];
        for (var i = 0; i < upperBound; ++i) {
            newComponents[i] = (components[i] || 0) | (otherComponents[i] || 0);
        }
        
        return new BigInteger(newComponents);
    },
    
    equals: function(other) {
        /// <summary>
        ///   Determines whether two BigIntegers equal each other.
        /// </summary>
        /// <param name="other" type="BigInteger">
        ///   The BigInteger with which to compare this BigInteger.
        /// </param>
        /// <returns type="Boolean" />
        
        this._checkArgument(other);
        
        if (!other.components) {
            other = new BigInteger(other);
        }
        
        var components = this.components;
        var otherComponents = other.components;
        
        var length = Math.max(components.length, otherComponents.length);
        
        for (var i = 0; i < length; ++i) {
            if ((components[i] || 0) !== (otherComponents[i] || 0)) {
                return false;
            }
        }
        
        return true;
    },
    
    notEquals: function(other) {
        /// <summary>
        ///   Determines whether two BigIntegers do not equal each other.
        /// </summary>
        /// <param name="other" type="BigInteger">
        ///   The BigInteger with which to compare this BigInteger.
        /// </param>
        /// <returns type="Boolean" />
        
        return !this.equals(other);
    },
    
    toString: function() {
        /// <summary>
        ///   Returns a string representation of this big integer.
        /// </summary>
        /// <returns type="String" />
        
        var components = this.components;
        
        if (components.length === 0) {
            return "";
        }
        
        var sb = components[components.length - 1].toString(16);
        
        for (var i = components.length - 2; i >= 0; --i) {
            var string = components[i].toString(16);
            
            if (string.length < 4) {
                string = $.repeat('0', 4 - string.length) + string;
            }
            
            sb += string;
        }
        
        return sb.replace(/^0{2,}$/, "0").replace(/^0{2,}/g, "");
    }
};

BigInteger.fromMultipleOfTwo = function(exponent) {
    var prefix = Math.pow(2, exponent % 4).toString();

    return new BigInteger(prefix + $.repeat("0", parseInt(exponent / 4)));
};

}});